'''
Distributed under the MIT License

Copyright (c) 2010 Bram Cohen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Contributors:
    Bram Cohen (bram@bitconjurer.org)

'''

from traceback import extract_stack, format_list
from time import time as defaulttime

class spam:
	def eggs(self):
		pass

method = type(spam().eggs)
nonet = type(None)

def logging(other, timefunc = defaulttime, log = None, prefix = '', self = None):
	t = type(other)
	if t in (int, bytes, str, tuple, nonet, bool, float):
		return other
	if log is None:
		log = []
	if t is dict:
		return DictLogger(timefunc, other, log, prefix)
	if t is list:
		return ListLogger(timefunc, other, log, prefix)
	if t is method:
		return lambda *a, **b: other.__func__(*([self] + list(a)), **b)
	return ObjLogger(timefunc, other, log, prefix)

def logged(t, timefunc = defaulttime):
	return lambda *a, **b: logging(t(*a, **b), timefunc)

def _dump(log):
	for assign, t, stack in log:
		print(assign)
		print('time %f' % t)
		print(''.join(format_list([x for x in stack if __name__ not in x[0]])))

class ObjLogger:
	def __init__(self, timefunc, other, log, prefix):
		object.__setattr__(self, '_time', timefunc)
		object.__setattr__(self, '_target', other)
		object.__setattr__(self, '_log', log)
		object.__setattr__(self, '_prefix', prefix)

	def __hash__(self):
		return self._target.__hash__()

	def __bool__(self):
		return (True if self._target else False)

	def __delattr__(self, key):
		if hasattr(key, '_target'):
			key = key._target
		self._log.append(('del ' + self._prefix + '.' + str(key), self._time(), extract_stack()))
		self._target.__delattr__(key)

	def __setattr__(self, key, value):
		if hasattr(key, '_target'):
			key = key._target
		if hasattr(value, '_target'):
			value = value._target
		self._log.append(((self._prefix + '.' + str(key) + ' = ' + str(value)), self._time(), extract_stack()))
		self._target.__setattr__(key, value)

	def __getattr__(self, key):
		if hasattr(key, '_target'):
			key = key._target
		return logging(getattr(self._target, key), self._time, self._log, self._prefix + '.' + str(key), self)

	def __repr__(self):
		return repr(self._target)

	def __str__(self):
		return str(self._target)

	def __eq__(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		return self._target == thing._target

	def dump(self):
		_dump(self._log)

class ListLogger:
	def __init__(self, timefunc, target, log, prefix):
		self._time = timefunc
		self._target = target
		self._log  = log
		self._prefix = prefix

	def _add(self, s):
		if hasattr(s, '_target'):
			s = s._target
		self._log.append((s, self._time(), extract_stack()))

	def dump(self):
		_dump(self._log)

	def __bool__(self):
		return (True if self._target else False)

	def __eq__(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		return self._target == thing

	def append(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		self._add(self._prefix  + '.append(' + str(thing) + ')')
		self._target.append(thing)
	
	def count(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		return self._target.count(thing)
	
	def extend(self, stuff):
		stuff2 = [(x if not hasattr(x, '_target') else x._target) for x in stuff]
		self._add(self._prefix + '.extend(' + str(stuff) + ')')
		self._target.extend(stuff2)
	
	def index(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		return self._target.index(thing)
	
	def insert(self, index, thing):
		if hasattr(index, '_target'):
			index = index._target
		if hasattr(thing, '_target'):
			thing = thing._target
		self._add(self._prefix + '.insert(' + str(index) + ', ' + str(thing) + ')')
		self._target.insert(index, thing)
	
	def pop(self, index):
		if hasattr(index, '_target'):
			index = index._target
		self._add(self._prefix + '.pop(' + str(index) + ')')
		return self._target.pop(index)
	
	def remove(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		self._add(self._prefix + '.remove(' + str(thing) + ')')
		if hasattr(thing, '_target'):
			thing = thing._target
		self._target.remove(thing)
	
	def reverse(self):
		self._add(self._prefix + '.reverse()')
		self._target.reverse()
	
	def sort(self):
		self._add(self._prefix + '.sort()')
		self._target.sort()

	def __len__(self):
		return len(self._target)
	
	def __repr__(self):
		return repr(self._target)
	
	def __str__(self):
		return str(self._target)
	
	def __contains__(self, thing):
		return thing in self._target
	
	def __getitem__(self, index):
		if hasattr(index, '_target'):
			index = index._target
		return logging(self._target[index], self._time, self._log, self._prefix + '[' + str(index) + ']')
	
	def __setitem__(self, index, thing):
		if hasattr(index, '_target'):
			index = index._target
		if hasattr(thing, '_target'):
			thing = thing._target
		self._add(self._prefix + '[' + str(index) + '] = ' + str(thing))
		self._target[index] = thing

	def __delitem__(self, key):
		if hasattr(key, '_target'):
			key = key._target
		self._add('del ' + self._prefix + '[' + str(key) + ']')
		del self._target[key]
	
class DictLogger:
	def __init__(self, timefunc, target, log, prefix):
		self._time = timefunc
		self._target = target
		self._log  = log
		self._prefix = prefix

	def _add(self, s):
		self._log.append((s, self._time(), extract_stack()))

	def dump(self):
		_dump(self._log)

	def clear(self):
		self._add(self._prefix + '.clear()')
		self._target.clear()
	
	def get(self, a, b = None):
		if hasattr(a, '_target'):
			a = a._target
		if b is None:
			return logging(self._target.get(a, b), self._time, self._log, self._prefix + '.get(' + str(a) + ')')
		else:
			return logging(self._target.get(a, b), self._time, self._log, self._prefix + '.get(' + str(a) + ',' + str(b) + ')')
	
	def items(self):
		return ((a, logging(b, self._time, self._log, self._prefix + '[' + str(a) + ']')) for (a, b) in self._target.items())
	
	def keys(self):
		return self._target.keys()
	
	def pop(self, key):
		if hasattr(key, '_target'):
			key = key._target
		self._add(self.prefix + '.pop(' + str(key) + ')')
		return self._target.pop(key)
	
	def values(self):
		return (logging(b, self._time, self._log, self._prefix + '[' + str(a) + ']') for (a, b) in self._target.items())
	
	def __bool__(self):
		return (True if self._target else False)

	def __eq__(self, thing):
		if hasattr(thing, '_target'):
			thing = thing._target
		return self._target == thing._target

	def __len__(self):
		return len(self._target)
	
	def __repr__(self):
		return repr(self._target)
	
	def __str__(self):
		return str(self._target)
	
	def __contains__(self, thing):
		return thing in self._target
	
	def __delitem__(self, key):
		if hasattr(key, '_target'):
			key = key._target
		self._add('del ' + self._prefix + '[' + str(key) + ']')
		del self._target[key]
	
	def __getitem__(self, index):
		if hasattr(index, '_target'):
			index = index._target
		return logging(self._target[index], self._time, self._log, self._prefix + '[' + str(index) + ']')
	
	def __setitem__(self, index, thing):
		if hasattr(index, '_target'):
			index = index._target
		if hasattr(thing, '_target'):
			thing = thing._target
		self._add(self._prefix + '[' + str(index) + '] = ' + str(thing))
		self._target[index] = thing

